{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# CW-Husky ADC Sampling Phase Exploration\n",
    "\n",
    "With CW-Husky, the relative phase between the target clock and the ADC sampling clock can be adjusted via `scope.clock.adc_phase`.\n",
    "\n",
    "`scope.clock.adc_phase` has a limited range which depends on the target clock frequency; the first and main purpose of this notebook is to teach the mechanics of phase adjustments.\n",
    "\n",
    "We use Husky's built-in logic analyzer to do so.\n",
    "\n",
    "The phase relationship between the target and ADC clocks depends on whether:\n",
    "1. Husky is generating the target clock;\n",
    "2. The target is generating its own clock, and Husky uses this clock to generate a synchronous ADC sampling clock.\n",
    "\n",
    "(There is a third scenario, where Husky does not have access to the target clock and is therefore sampling asynchronously; in this scenario `scope.clock.adc_phase` is irrelevant.)\n",
    "\n",
    "The second purpose of this notebook is to explain the changes to clock generation that were done to resolve issues [490](https://github.com/newaetech/chipwhisperer/issues/490), [499](https://github.com/newaetech/chipwhisperer/issues/499), and [501](https://github.com/newaetech/chipwhisperer/issues/501). The intent is to help you understand how these changes affect the clock phase, and whether and how you can make updates to your existing ChipWhisperer capture scripts. These issues were fixed on the ChipWhisperer develop branch in November 2024.\n",
    "\n",
    "## Part 1: Husky Generates the Target Clock.\n",
    "\n",
    "No target is required for this part."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "SCOPE=\"OPENADC\"\n",
    "PLATFORM=\"CWHUSKY\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import chipwhisperer as cw\n",
    "scope = cw.scope()\n",
    "scope.default_setup()\n",
    "\n",
    "# avoid warnings when we slightly exceed the max clock spec:\n",
    "scope.trace.clock._warning_frequency = 255e6"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "EXTCLK = False\n",
    "target = None\n",
    "scope.clock.clkgen_src = 'system'\n",
    "\n",
    "scope.LA.enabled = True\n",
    "scope.LA.clk_source = 'pll'\n",
    "scope.LA.clkgen_enabled = False\n",
    "scope.LA.oversampling_factor = 1\n",
    "scope.LA.clkgen_enabled = True\n",
    "scope.LA.capture_group = 'CW 20-pin'\n",
    "scope.LA.capture_depth = 500\n",
    "assert scope.LA.locked"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We define a convenience function for setting the clock parameters. It also sets the `scope.LA` sampling rate accordingly for maximum precision."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def set_freq(freq, adc_mul, adc_phase):\n",
    "    scope.clock.adc_mul = adc_mul\n",
    "    scope.clock.clkgen_freq = freq\n",
    "    scope.clock.adc_phase = adc_phase\n",
    "\n",
    "    oversamp = int(250e6//freq)\n",
    "    scope.LA.clkgen_enabled = False\n",
    "    scope.LA.oversampling_factor = oversamp\n",
    "    scope.LA.clkgen_enabled = True\n",
    "    assert scope.LA.locked"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's start with a simple example: our standard 7.37 MHz clock, `scope.clock.adc_mul = 1`, and `scope.clock.adc_phase = 0`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "set_freq(7.37e6, 1, 0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "These methods capture the target and ADC clocks using `scope.LA`; we'll use them many times throughout this notebook:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def find0to1trans(data):\n",
    "    pattern = [0,1]\n",
    "    return [i for i in range(0,len(data)) if list(data[i:i+len(pattern)])==pattern]\n",
    "\n",
    "def get_clocks():\n",
    "    done = False\n",
    "    count = 0\n",
    "    samples = scope.LA.oversampling_factor * 4\n",
    "    while not done and count < 30:\n",
    "        scope.LA.arm()\n",
    "        scope.LA.trigger_now()\n",
    "        raw = scope.LA.read_capture_data()\n",
    "        adcclock = scope.LA.extract(raw, 8)\n",
    "        if EXTCLK:\n",
    "            refclock = scope.LA.extract(raw, 4)\n",
    "        else:\n",
    "            refclock = scope.LA.extract(raw, 5)\n",
    "        edges = find0to1trans(refclock)\n",
    "        if len(edges) > 1:\n",
    "            ref_edge = edges[1]\n",
    "        else:\n",
    "            ref_edge = edges[0]    \n",
    "        try:\n",
    "            #adc_ref_delta = find0to1trans(adcclock[ref_edge:])[0]\n",
    "            adc_edges = find0to1trans(adcclock)\n",
    "            adc_ref_delta = abs(min(adc_edges, key=lambda x:abs(x-ref_edge)) - ref_edge)\n",
    "            done = True\n",
    "        except:\n",
    "            # sometimes (rarely) the ADC clock comes back all zeros; could be an issue with the PLL or with the LA?\n",
    "            if all(c == 0 for c in adcclock):\n",
    "                adcclock = 'all zeros'\n",
    "            print('could not find delta; ref_edge=%3d, lock status=%s; adcclock=%s; trying again' % (ref_edge, scope.clock.pll.pll_locked, adcclock))\n",
    "            assert scope.LA.locked\n",
    "            assert scope.clock.pll.pll_locked\n",
    "            time.sleep(0.5)\n",
    "            count += 1\n",
    "    return adc_ref_delta, adcclock[edges[0]:edges[0]+samples], refclock[edges[0]:edges[0]+samples]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's see what we get:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "delta, adcclock, refclock = get_clocks()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bokeh.plotting import figure, show\n",
    "from bokeh.resources import INLINE\n",
    "from bokeh.io import output_notebook\n",
    "from bokeh.models import Span, Legend, LegendItem\n",
    "import numpy as np\n",
    "output_notebook(INLINE)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "samples = len(adcclock)\n",
    "xrange = list(range(samples))\n",
    "p = figure(width=1800, height=400)\n",
    "\n",
    "refline = p.line(xrange, refclock, line_color='blue')\n",
    "adcline = p.line(xrange, adcclock-2, line_color='green')\n",
    "difline = p.line(xrange, abs(refclock-adcclock)-4, line_color='red')\n",
    "\n",
    "legend = Legend(items=[\n",
    "    LegendItem(label='target clock', renderers=[refline]),\n",
    "    LegendItem(label='ADC clock', renderers=[adcline]),\n",
    "    LegendItem(label='difference between clocks', renderers=[difline]),\n",
    "])\n",
    "p.add_layout(legend)\n",
    "show(p)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The clocks should be coincident or nearly so. Now let's play with `scope.clock.adc_phase`.\n",
    "\n",
    "The [CDCI6214](https://www.ti.com/product/CDCI6214) is the PLL that's generating the clocks for CW-Husky, and it allows its output clocks to be delayed by relatively small sub-period steps; by delaying either one or both of the target and ADC clocks, we can obtain a different phase relationship between these two clocks.\n",
    "\n",
    "The step size depends on the clock frequency (not in a straightforward/linear way: it depends on the PLL's settings). Query this property to know the current step size, in picoseconds:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.clock.adc_phase_step_size"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now the phase itself is set via `scope.clock.adc_phase`, which is expressed in percentage of the target clock period.\n",
    "\n",
    "Let's measure and plot a few different values. We'll shift the ADC clock +/- 25% of the clock period:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "set_freq(7.37e6, 1, +25)\n",
    "delta, adcclock_p25, refclock_p10 = get_clocks()\n",
    "\n",
    "set_freq(7.37e6, 1, -25)\n",
    "delta, adcclock_m25, refclock_m10 = get_clocks()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "samples = len(adcclock)\n",
    "xrange = list(range(samples))\n",
    "p = figure(width=1800, height=400)\n",
    "\n",
    "refline = p.line(xrange, refclock, line_color='blue')\n",
    "adcline = p.line(xrange, adcclock-2, line_color='green')\n",
    "adcline_p25 = p.line(xrange, adcclock_p25-4, line_color='orange')\n",
    "adcline_m25 = p.line(xrange, adcclock_m25-6, line_color='red')\n",
    "\n",
    "\n",
    "legend = Legend(items=[\n",
    "    LegendItem(label='target clock', renderers=[refline]),\n",
    "    LegendItem(label='ADC clock', renderers=[adcline]),\n",
    "    LegendItem(label='ADC clock, scope.clock.adc_phase = +25', renderers=[adcline_p25]),\n",
    "    LegendItem(label='ADC clock, scope.clock.adc_phase = -25', renderers=[adcline_m25]),\n",
    "])\n",
    "p.add_layout(legend)\n",
    "show(p)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`scope.clock.adc_phase` has a limited range: the highest it can go depends on the target clock frequency (again, not in a straightforward way). The relationship between max phase and clock frequency is not a simple one, so we provide the `scope.clock.pll.max_phase_percent` property:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.clock.pll.max_phase_percent"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you try to set `scope.clock.adc_phase` above this, you'll get an error.\n",
    "\n",
    "We've used a fixed clock and `adc_mul` values to show the basics here, but you're not limited to these values. Play around with them as you like, keeping in mind that we set the maximum `scope.LA` sampling rate to 250 MS/s; the faster the clocks, the lower the granularity."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "set_freq(8e6, 2, +15)\n",
    "delta, adcclock, refclock = get_clocks()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "set_freq(15e6, 1, 0)\n",
    "delta, adcclock, refclock = get_clocks()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "samples = len(adcclock)\n",
    "xrange = list(range(samples))\n",
    "p = figure(width=1800, height=400)\n",
    "\n",
    "refline = p.line(xrange, refclock, line_color='blue')\n",
    "adcline = p.line(xrange, adcclock-2, line_color='green')\n",
    "\n",
    "legend = Legend(items=[\n",
    "    LegendItem(label='target clock', renderers=[refline]),\n",
    "    LegendItem(label='ADC clock', renderers=[adcline]),\n",
    "])\n",
    "\n",
    "p.add_layout(legend)\n",
    "show(p)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Clock phase prior to issue [490](https://github.com/newaetech/chipwhisperer/issues/490) being fixed:\n",
    "\n",
    "*In most situations are for most users, you don't need to worry about this.*\n",
    "\n",
    "*In particular, if you never set `scope.clock.adc_phase != 0` and always used `scope.clock.clkgen_src = system`, you are not affected.*\n",
    "\n",
    "When Husky generates the target clock, the root cause behind issue [490](https://github.com/newaetech/chipwhisperer/issues/490) is that when the user changed `scope.clock.adc_mul`, the PLL driver code would try very hard to find PLL parameters which would not affect (i.e. momentarily drop) the target clock.\n",
    "\n",
    "Though well-intentioned, this had the side-effect that going from a set of clock settings \"A\" to a set of clock settings \"B\" could yield different PLL settings than going from \"A\" to some other settings \"X\" before ending on \"B\".\n",
    "\n",
    "In other words, an innocuous change to a capture script -- adding or remove \"X\" in the example above -- could result in different PLL settings, even though the user is requesting a final set of clock settings \"B\" in both cases.\n",
    "\n",
    "Why is this a problem? We saw above that the dimension of the `adc_phase` step depends on PLL settings. Prior to November 2024, `scope.clock.adc_phase` was specified as a dimensionless integer, whose actual meaning (in terms of target/ADC clock phase) depends on the PLL parameters. If you had `scope.clock.adc_phase = 0`, then you can ignore all this because 0 is always 0, in both cases. But if you used a non-zero `adc_phase`, your actual phase may have been inconsistent.\n",
    "\n",
    "To avoid this situation, `scope.clock.adc_phase` is now expressed in percentage of the target clock period. \n",
    "\n",
    "The phase can still be specified as a raw integer in the range -31 to +31 if you wish to attempt to replicate a previously used phase, via `scope.clock.adc_phase_raw`. (Previously, the phase was a integer in the range -255 to +255; simply scale from [-255,255] to [-31,31] linearly.)\n",
    "\n",
    "You can use this notebook with an older ChipWhisperer release or commit to visualize what phase you had, and find the best parameters for you desired phase for the current ChipWhisperer code."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Part 2: The Target Generates its Own Clock\n",
    "\n",
    "Here we'll use a CW305 to provide a clock to Husky. You can use a different target to provide the clock; simply modify the `set_target_freq()` method below to what's required to set your target's clock frequency."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "EXTCLK = True\n",
    "scope.clock.clkgen_freq = 10e6\n",
    "scope.clock.clkgen_src = 'extclk'\n",
    "scope.LA.clk_source = 'target'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def set_target_freq(freq):\n",
    "    # this is for setting the CW305's clock; modify as needed for different targets\n",
    "    global target\n",
    "    if target is None:\n",
    "        target = cw.target(scope, cw.targets.CW305, fpga_id='100t')\n",
    "        target.pll.pll_enable_set(True)\n",
    "        target.pll.pll_outenable_set(False, 0)\n",
    "        target.pll.pll_outenable_set(True, 1)\n",
    "        target.pll.pll_outenable_set(False, 2)\n",
    "        target.pll.pll_outfreq_set(10e6, 1)\n",
    "    target.pll.pll_outfreq_set(freq, 1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We'll modify `set_freq()` so that it also sets the target's frequency:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def set_freq(freq, adc_mul, adc_phase):\n",
    "    set_target_freq(freq)\n",
    "    scope.clock.adc_mul = adc_mul\n",
    "    scope.clock.clkgen_freq = freq\n",
    "    scope.clock.adc_phase = adc_phase\n",
    "\n",
    "    oversamp = int(250e6//freq)\n",
    "    scope.LA.clkgen_enabled = False\n",
    "    scope.LA.oversampling_factor = oversamp\n",
    "    scope.LA.clkgen_enabled = True\n",
    "    assert scope.LA.locked"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "set_freq(10e6, 1, 0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's collect measurements and plot:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "delta, adcclock, refclock = get_clocks()\n",
    "\n",
    "samples = len(adcclock)\n",
    "xrange = list(range(samples))\n",
    "p = figure(width=1800, height=400)\n",
    "\n",
    "refline = p.line(xrange, refclock, line_color='blue')\n",
    "adcline = p.line(xrange, adcclock-2, line_color='green')\n",
    "\n",
    "legend = Legend(items=[\n",
    "    LegendItem(label='target clock', renderers=[refline]),\n",
    "    LegendItem(label='ADC clock', renderers=[adcline]),\n",
    "])\n",
    "p.add_layout(legend)\n",
    "show(p)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Why the large phase difference with `scope.clock.adc_phase = 0`? The clock distribution network in this scenario, illustrated below, is far from simple:\n",
    "1. the target generates a clock, which is sent to Husky on the HS1 pin;\n",
    "2. the Husky FPGA takes this clock and passes it onto the CDCI6214 PLL, for it to use as a reference;\n",
    "3. the PLL generates the ADC sampling clock from this reference and sends it to the ADC;\n",
    "4. the ADC sends a copy of this clock back to the Husky FPGA.\n",
    "\n",
    "The actual target and ADC clocks are highlighted green. The versions of these clocks that are sampled by `scope.LA` are highlighted yellow.\n",
    "\n",
    "Each black box in the diagram is a discrete chip, with associated pad delays and routing delays.\n",
    "\n",
    "That's a lot of clock routing, across three different chips. Husky's `scope.LA` isn't measuring these clock where they are used; they're measured somewhere further along.\n",
    "\n",
    "<img src=\"img/husky_clocks_phase.png\" width=\"30%\"/>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The phase relationship between the target and ADC clocks, **where they are measured by `scope.LA`**, when `scope.clock.adc_phase = 0` is as follows: the rising edge of the ADC clock follows approximately 10ns after the *falling* edge of the target clock.\n",
    "\n",
    "The ~10ns offset is independent of the target (or ADC) clock frequency. To verify, we measure the clocks when HS1 is swept from 10 MHz to 30 MHz:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from tqdm.notebook import tnrange, tqdm\n",
    "import numpy as np\n",
    "import time\n",
    "import random\n",
    "\n",
    "adcs = []\n",
    "refs = []\n",
    "\n",
    "for freq in tnrange(int(10e6), int(30e6), int(2e5)):\n",
    "    set_freq(freq, 1, 0)\n",
    "    delta, adc, ref = get_clocks()\n",
    "    adcs.append(adc)\n",
    "    refs.append(ref)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def update_plot(f):\n",
    "    start = find1to0trans(refs[f])[0]\n",
    "    S1.data_source.data['y'] = refs[f][start-1:start-1+samples] + 2\n",
    "    S2.data_source.data['y'] = adcs[f][start-1:start-1+samples] + 0  \n",
    "    push_notebook()\n",
    "\n",
    "def find1to0trans(data):\n",
    "    pattern = [1,0]\n",
    "    return [i for i in range(0,len(data)) if list(data[i:i+len(pattern)])==pattern]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import interact, Layout\n",
    "from bokeh.io import push_notebook\n",
    "from bokeh.models import Span, Legend, LegendItem\n",
    "\n",
    "p = 0\n",
    "\n",
    "S = figure(width=1800)\n",
    "\n",
    "samples = int(len(adcs[-1])*3/4)+1\n",
    "xrange = list(range(samples))\n",
    "\n",
    "start = find1to0trans(refs[p])[0]\n",
    "\n",
    "S1 = S.line(xrange, refs[p][start-1:start-1+samples] + 2, line_color='black')\n",
    "S2 = S.line(xrange, adcs[p][start-1:start-1+samples] + 0, line_color='blue')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "show(S, notebook_handle=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "interact(update_plot, f=(0, len(adcs)-1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Clock phase prior to issues [499](https://github.com/newaetech/chipwhisperer/issues/499) and [501](https://github.com/newaetech/chipwhisperer/issues/501) being fixed:\n",
    "\n",
    "*In most situations are for most users, you don't need to worry about this.*\n",
    "\n",
    "*None of our notebooks which use `scope.clock.clkgen_src = 'extclk'` (AES and ECC FPGA target notebooks) required modifications.*\n",
    "\n",
    "\n",
    "#### Issue [499](https://github.com/newaetech/chipwhisperer/issues/499):\n",
    "It was previously possible that Husky would generate an ADC clock that was close to, but not exactly, an `scope.clock.adc_mul` multiple of the target clock.\n",
    "\n",
    "Obviously this would result in a constantly changing phase between the ADC and target clocks.\n",
    "\n",
    "Follow the issue link above to learn how to find out whether your particular configuration of clocks ran into this issue.\n",
    "\n",
    "#### Issue [501](https://github.com/newaetech/chipwhisperer/issues/501):\n",
    "The phase offset would have a frequency-dependent component (i.e. in the interactive plot above, the ADC clock rising edge would be moving around).\n",
    "\n",
    "You can use this notebook with an older ChipWhisperer release or commit to visualize what phase you had, and find the `scope.clock.adc_phase` which most closely matches this with the current ChipWhisperer code.\n",
    "\n",
    "Among the `scope.clock.adc_phase` issues, this is the one most likely to impact you (**if** you use `scope.clock.clkgen_src = 'extclk'`). The impact would be largest if you use `scope.clock.adc_mul = 1`, and used a clock frequency that results in a significantly different phase post-fix."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python (venv39)",
   "language": "python",
   "name": "venv39"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
